import pkg from 'lodash';
const { trim } = pkg;
import pptxgen from 'pptxgenjs';
import tinycolor from 'tinycolor2';
import type {
  Slide,
  SlideTheme,
  PPTElementOutline,
  PPTElementShadow,
  PPTElementLink,
  PPTRaw,
} from './types/slides.js';
import {
  getElementRange,
  getLineElementPath,
  getTableSubThemeColor,
} from './utils/element.js';
import { type AST, toAST, parseRemarkHTML } from './utils/htmlParser.js';
import { type SvgPoints, toPoints } from './utils/svgPathParser.js';
import { svg2Base64, generateSvgString } from './utils/svg2Base64.js';

export interface ExportOptions {
  masterOverwrite?: boolean;
  ignoreMedia?: boolean;
  onProgress?: (progress: ProgressInfo) => void;
}

export interface ProgressInfo {
  current: number; // 当前处理的幻灯片数
  total: number; // 总幻灯片数
  percentage: number; // 完成百分比 (0-100)
  completed: boolean; // 是否完成
}

interface ExportImageConfig {
  quality: number;
  width: number;
  fontEmbedCSS?: string;
}

// 环境检测工具
class EnvironmentDetector {
  /**
   * 检测是否为 Node.js 环境
   */
  static isNodeJS(): boolean {
    return (
      typeof process !== 'undefined' &&
      process.versions != null &&
      process.versions.node != null
    );
  }

  /**
   * 检测是否为浏览器环境
   */
  static isBrowser(): boolean {
    return typeof window !== 'undefined' && typeof document !== 'undefined';
  }

  /**
   * 检测是否为 Deno 环境
   */
  static isDeno(): boolean {
    return typeof (globalThis as any).Deno !== 'undefined';
  }

  /**
   * 检测是否为 Bun 环境
   */
  static isBun(): boolean {
    return typeof (globalThis as any).Bun !== 'undefined';
  }

  /**
   * 获取当前运行环境
   */
  static getEnvironment(): 'node' | 'browser' | 'deno' | 'bun' | 'unknown' {
    if (this.isNodeJS()) return 'node';
    if (this.isBrowser()) return 'browser';
    if (this.isDeno()) return 'deno';
    if (this.isBun()) return 'bun';
    return 'unknown';
  }

  /**
   * 检测是否支持 Buffer
   */
  static supportsBuffer(): boolean {
    return this.isNodeJS() || this.isBun();
  }

  /**
   * 检测是否支持 Blob
   */
  static supportsBlob(): boolean {
    return this.isBrowser() || this.isDeno();
  }

  /**
   * 检测是否支持 ArrayBuffer
   */
  static supportsArrayBuffer(): boolean {
    return typeof ArrayBuffer !== 'undefined';
  }

  /**
   * 检测是否支持 Uint8Array
   */
  static supportsUint8Array(): boolean {
    return typeof Uint8Array !== 'undefined';
  }
}

// 格式化颜色值为 透明度 + HexString，供pptxgenjs使用
const formatColor = (_color: string) => {
  if (!_color) {
    return {
      alpha: 0,
      color: '#000000',
    };
  }

  const c = tinycolor(_color);
  const alpha = c.getAlpha();
  const color = alpha === 0 ? '#ffffff' : c.setAlpha(1).toHexString();
  return {
    alpha,
    color,
  };
};

type FormatColor = ReturnType<typeof formatColor>;

// 将HTML字符串格式化为pptxgenjs所需的格式
// 核心思路：将HTML字符串按样式分片平铺，每个片段需要继承祖先元素的样式信息，遇到块级元素需要换行
const formatHTML = (
  html: string,
  ratioPx2Pt: number,
  defaultFontSize: number
) => {
  const ast = toAST(html);
  let bulletFlag = false;
  let indent = 0;

  const slices: pptxgen.TextProps[] = [];
  const parse = (obj: AST, baseStyleObj: { [key: string]: string } = {}) => {
    for (const item of obj) {
      const isBlockTag =
        item.tagName && ['div', 'li', 'p'].includes(item.tagName);

      if (isBlockTag && slices.length) {
        const lastSlice = slices[slices.length - 1];
        if (!lastSlice.options) lastSlice.options = {};
        lastSlice.options.breakLine = true;
      }

      const styleObj = { ...baseStyleObj };
      const styleAttr = item.attributes?.find(attr => attr.key === 'style');
      if (styleAttr && styleAttr.value) {
        const styleArr = styleAttr.value.split(';');
        for (const styleItem of styleArr) {
          const [_key, _value] = styleItem.split(': ');
          const [key, value] = [trim(_key), trim(_value)];
          if (key && value) styleObj[key] = value;
        }
      }

      if (item.tagName) {
        if (item.tagName === 'em') {
          styleObj['font-style'] = 'italic';
        }
        if (item.tagName === 'strong') {
          styleObj['font-weight'] = 'bold';
        }
        if (item.tagName === 'sup') {
          styleObj['vertical-align'] = 'super';
        }
        if (item.tagName === 'sub') {
          styleObj['vertical-align'] = 'sub';
        }
        if (item.tagName === 'a') {
          const attr = item.attributes?.find(attr => attr.key === 'href');
          styleObj['href'] = attr?.value || '';
        }
        if (item.tagName === 'ul') {
          styleObj['list-type'] = 'ul';
        }
        if (item.tagName === 'ol') {
          styleObj['list-type'] = 'ol';
        }
        if (item.tagName === 'li') {
          bulletFlag = true;
        }
        if (item.tagName === 'p') {
          const dataIndentAttr = item.attributes?.find(
            attr => attr.key === 'data-indent'
          );
          if (dataIndentAttr && dataIndentAttr.value)
            indent = +dataIndentAttr.value;
        }
      }

      if (item.tagName === 'br') {
        slices.push({ text: '', options: { breakLine: true } });
      } else if (item.content) {
        const text = item.content
          .replace(/&nbsp;/g, ' ')
          .replace(/&gt;/g, '>')
          .replace(/&lt;/g, '<')
          .replace(/&amp;/g, '&')
          .replace(/\n/g, '');
        const options: pptxgen.TextPropsOptions = {};

        if (styleObj['font-size']) {
          options.fontSize = parseInt(styleObj['font-size']) / ratioPx2Pt;
        }
        if (styleObj['color']) {
          options.color = formatColor(styleObj['color']).color;
        }
        if (styleObj['background-color']) {
          options.highlight = formatColor(styleObj['background-color']).color;
        }
        if (styleObj['text-decoration-line']) {
          if (styleObj['text-decoration-line'].indexOf('underline') !== -1) {
            options.underline = {
              color: options.color || '#000000',
              style: 'sng',
            };
          }
          if (styleObj['text-decoration-line'].indexOf('line-through') !== -1) {
            options.strike = 'sngStrike';
          }
        }
        if (styleObj['text-decoration']) {
          if (styleObj['text-decoration'].indexOf('underline') !== -1) {
            options.underline = {
              color: options.color || '#000000',
              style: 'sng',
            };
          }
          if (styleObj['text-decoration'].indexOf('line-through') !== -1) {
            options.strike = 'sngStrike';
          }
        }
        if (styleObj['vertical-align']) {
          if (styleObj['vertical-align'] === 'super')
            options.superscript = true;
          if (styleObj['vertical-align'] === 'sub') options.subscript = true;
        }
        if (styleObj['text-align'])
          options.align = styleObj['text-align'] as pptxgen.HAlign;
        if (styleObj['font-weight'])
          options.bold = styleObj['font-weight'] === 'bold';
        if (styleObj['font-style'])
          options.italic = styleObj['font-style'] === 'italic';
        if (styleObj['font-family']) options.fontFace = styleObj['font-family'];
        if (styleObj['href']) options.hyperlink = { url: styleObj['href'] };

        if (bulletFlag && styleObj['list-type'] === 'ol') {
          options.bullet = {
            type: 'number',
            indent: (options.fontSize || defaultFontSize) * 1.25,
          };
          options.paraSpaceBefore = 0.1;
          bulletFlag = false;
        }
        if (bulletFlag && styleObj['list-type'] === 'ul') {
          options.bullet = {
            indent: (options.fontSize || defaultFontSize) * 1.25,
          };
          options.paraSpaceBefore = 0.1;
          bulletFlag = false;
        }
        if (indent) {
          options.indentLevel = indent;
          indent = 0;
        }

        slices.push({ text, options });
      } else if (item.children) parse(item.children, styleObj);
    }
  };
  parse(ast);
  return slices;
};

type Points = Array<
  | { x: number; y: number; moveTo?: boolean }
  | {
      x: number;
      y: number;
      curve: {
        type: 'arc';
        hR: number;
        wR: number;
        stAng: number;
        swAng: number;
      };
    }
  | {
      x: number;
      y: number;
      curve: { type: 'quadratic'; x1: number; y1: number };
    }
  | {
      x: number;
      y: number;
      curve: {
        type: 'cubic';
        x1: number;
        y1: number;
        x2: number;
        y2: number;
      };
    }
  | { close: true }
>;

// 将SVG路径信息格式化为pptxgenjs所需要的格式
const formatPoints = (
  points: SvgPoints,
  ratioPx2Inch: number,
  scale = { x: 1, y: 1 }
): Points => {
  return points.map(point => {
    if (point.close !== undefined) {
      return { close: true };
    } else if (point.type === 'M') {
      return {
        x: (point.x / ratioPx2Inch) * scale.x,
        y: (point.y / ratioPx2Inch) * scale.y,
        moveTo: true,
      };
    } else if (point.curve) {
      if (point.curve.type === 'cubic') {
        return {
          x: (point.x / ratioPx2Inch) * scale.x,
          y: (point.y / ratioPx2Inch) * scale.y,
          curve: {
            type: 'cubic',
            x1: ((point.curve.x1 as number) / ratioPx2Inch) * scale.x,
            y1: ((point.curve.y1 as number) / ratioPx2Inch) * scale.y,
            x2: ((point.curve.x2 as number) / ratioPx2Inch) * scale.x,
            y2: ((point.curve.y2 as number) / ratioPx2Inch) * scale.y,
          },
        };
      } else if (point.curve.type === 'quadratic') {
        return {
          x: (point.x / ratioPx2Inch) * scale.x,
          y: (point.y / ratioPx2Inch) * scale.y,
          curve: {
            type: 'quadratic',
            x1: ((point.curve.x1 as number) / ratioPx2Inch) * scale.x,
            y1: ((point.curve.y1 as number) / ratioPx2Inch) * scale.y,
          },
        };
      }
    }
    return {
      x: (point.x / ratioPx2Inch) * scale.x,
      y: (point.y / ratioPx2Inch) * scale.y,
    };
  });
};

// 获取阴影配置
const getShadowOption = (
  shadow: PPTElementShadow,
  ratioPx2Pt: number
): pptxgen.ShadowProps => {
  const c = formatColor(shadow.color);
  const { h, v } = shadow;

  let offset = 4;
  let angle = 45;

  if (h === 0 && v === 0) {
    offset = 4;
    angle = 45;
  } else if (h === 0) {
    if (v > 0) {
      offset = v;
      angle = 90;
    } else {
      offset = -v;
      angle = 270;
    }
  } else if (v === 0) {
    if (h > 0) {
      offset = h;
      angle = 1;
    } else {
      offset = -h;
      angle = 180;
    }
  } else if (h > 0 && v > 0) {
    offset = Math.max(h, v);
    angle = 45;
  } else if (h > 0 && v < 0) {
    offset = Math.max(h, -v);
    angle = 315;
  } else if (h < 0 && v > 0) {
    offset = Math.max(-h, v);
    angle = 135;
  } else if (h < 0 && v < 0) {
    offset = Math.max(-h, -v);
    angle = 225;
  }

  return {
    type: 'outer',
    color: c.color.replace('#', ''),
    opacity: c.alpha,
    blur: shadow.blur / ratioPx2Pt,
    offset,
    angle,
  };
};

const dashTypeMap = {
  solid: 'solid',
  dashed: 'dash',
  dotted: 'sysDot',
};

// 获取边框配置
const getOutlineOption = (
  outline: PPTElementOutline,
  ratioPx2Pt: number
): pptxgen.ShapeLineProps => {
  const c = formatColor(outline?.color || '#000000');

  return {
    color: c.color,
    transparency: (1 - c.alpha) * 100,
    width: (outline.width || 1) / ratioPx2Pt,
    dashType: outline.style
      ? (dashTypeMap[outline.style] as 'solid' | 'dash' | 'sysDot')
      : 'solid',
  };
};

// 获取超链接配置
const getLinkOption = (
  link: PPTElementLink,
  slides: Slide[]
): pptxgen.HyperlinkProps | null => {
  const { type, target } = link;
  if (type === 'web') return { url: target };
  if (type === 'slide') {
    const index = slides.findIndex(slide => slide.id === target);
    if (index !== -1) return { slide: index + 1 };
  }

  return null;
};

// 判断是否为Base64图片地址
const isBase64Image = (url: string) => {
  const regex = /^data:image\/[^;]+;base64,/;
  return url.match(regex) !== null;
};

// 判断是否为SVG图片地址
const isSVGImage = (url: string) => {
  const isSVGBase64 = /^data:image\/svg\+xml;base64,/.test(url);
  const isSVGUrl = /\.svg$/.test(url);
  return isSVGBase64 || isSVGUrl;
};

export class PPTXExporter {
  private pptData: PPTRaw;
  private options: ExportOptions;
  private environment: string;

  constructor(pptData: PPTRaw, options: ExportOptions = {}) {
    this.pptData = pptData;
    this.options = {
      masterOverwrite: false,
      ignoreMedia: false,
      ...options,
    };
    this.environment = EnvironmentDetector.getEnvironment();
  }

  /**
   * 获取当前运行环境
   */
  public getEnvironment(): string {
    return this.environment;
  }

  /**
   * 报告导出进度
   */
  private reportProgress(
    current: number,
    total: number,
    completed: boolean = false
  ): void {
    if (this.options.onProgress) {
      const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
      this.options.onProgress({
        current,
        total,
        percentage,
        completed,
      });
    }
  }

  /**
   * 获取支持的导出格式
   */
  public getSupportedFormats(): string[] {
    const formats: string[] = [];

    if (EnvironmentDetector.supportsBuffer()) {
      formats.push('nodebuffer');
    }

    if (EnvironmentDetector.supportsBlob()) {
      formats.push('blob');
    }

    if (EnvironmentDetector.supportsArrayBuffer()) {
      formats.push('arraybuffer');
    }

    if (EnvironmentDetector.supportsUint8Array()) {
      formats.push('uint8array');
    }

    // base64 和 binarystring 在所有环境中都支持
    formats.push('base64', 'binarystring');

    return formats;
  }

  private _export(): pptxgen {
    const pptx = new pptxgen();

    // 初始化进度追踪
    this.reportProgress(0, 1);

    if (
      !this.pptData ||
      !this.pptData.slides ||
      !Array.isArray(this.pptData.slides)
    ) {
      throw new Error('PPT data must contain a valid slides array');
    }

    // 如果 slides 是空数组，创建一个空的 PPTX 文件
    if (this.pptData.slides.length === 0) {
      const pptx = new pptxgen();
      pptx.author = 'PPTX Exporter';
      pptx.company = 'Devoink';
      pptx.title = this.pptData.title || 'Empty Presentation';
      this.reportProgress(1, 1, true);
      return pptx;
    }

    const { title, width, height, theme, slides: _slides } = this.pptData;

    const { masterOverwrite = false, ignoreMedia = false } = this.options;
    const viewportSize = width;
    const viewportRatio = height / width;

    const defaultFontSize = 16;

    const ratioPx2Inch = 96 * (viewportSize / 960);
    const ratioPx2Pt = (96 / 72) * (viewportSize / 960);

    if (viewportRatio === 0.625) pptx.layout = 'LAYOUT_16x10';
    else if (viewportRatio === 0.75) pptx.layout = 'LAYOUT_4x3';
    else if (viewportRatio === 0.70710678) {
      pptx.defineLayout({ name: 'A3', width: 10, height: 7.0710678 });
      pptx.layout = 'A3';
    } else if (viewportRatio === 1.41421356) {
      pptx.defineLayout({ name: 'A3_V', width: 10, height: 14.1421356 });
      pptx.layout = 'A3_V';
    } else pptx.layout = 'LAYOUT_16x9';

    if (masterOverwrite) {
      const { color: bgColor, alpha: bgAlpha } = formatColor(
        theme.backgroundColor
      );
      pptx.defineSlideMaster({
        title: 'PPTIST_MASTER',
        background: { color: bgColor, transparency: (1 - bgAlpha) * 100 },
      });
    }

    const totalSlides = _slides.length;
    this.reportProgress(0, totalSlides);

    for (let slideIndex = 0; slideIndex < _slides.length; slideIndex++) {
      const slide = _slides[slideIndex];
      const pptxSlide = pptx.addSlide();

      this.reportProgress(slideIndex + 1, totalSlides);

      if (slide.background) {
        const background = slide.background;
        if (background.type === 'image' && background.image) {
          if (isSVGImage(background.image.src)) {
            pptxSlide.addImage({
              data: background.image.src,
              x: 0,
              y: 0,
              w: viewportSize / ratioPx2Inch,
              h: (viewportSize * viewportRatio) / ratioPx2Inch,
            });
          } else if (isBase64Image(background.image.src)) {
            pptxSlide.background = { data: background.image.src };
          } else {
            pptxSlide.background = { path: background.image.src };
          }
        } else if (background.type === 'solid' && background.color) {
          const c = formatColor(background.color);
          pptxSlide.background = {
            color: c.color,
            transparency: (1 - c.alpha) * 100,
          };
        } else if (background.type === 'gradient' && background.gradient) {
          const colors = background.gradient.colors;
          const color1 = colors[0].color;
          const color2 = colors[colors.length - 1].color;
          const color = tinycolor.mix(color1, color2).toHexString();
          const c = formatColor(color);
          pptxSlide.background = {
            color: c.color,
            transparency: (1 - c.alpha) * 100,
          };
        }
      }

      // 备注处理 - 使用替代方案
      if (slide.remark) {
        const textArray = parseRemarkHTML(slide.remark);
        pptxSlide.addNotes(textArray.join('\n'));
      }

      if (!slide.elements) continue;

      for (const el of slide.elements) {
        if (el.type === 'text') {
          const textProps = formatHTML(el.content, ratioPx2Pt, defaultFontSize);

          const options: pptxgen.TextPropsOptions = {
            x: el.left / ratioPx2Inch,
            y: el.top / ratioPx2Inch,
            w: el.width / ratioPx2Inch,
            h: el.height / ratioPx2Inch,
            fontSize: defaultFontSize / ratioPx2Pt,
            fontFace: '微软雅黑',
            color: '#000000',
            valign: 'top',
            margin: 10 / ratioPx2Pt,
            paraSpaceBefore: 5 / ratioPx2Pt,
            lineSpacingMultiple: 1.5 / 1.25,
            autoFit: true,
          };
          if (el.rotate) options.rotate = el.rotate;
          if (el.wordSpace) options.charSpacing = el.wordSpace / ratioPx2Pt;
          if (el.lineHeight) options.lineSpacingMultiple = el.lineHeight / 1.25;
          if (el.fill) {
            const c = formatColor(el.fill);
            const opacity = el.opacity === undefined ? 1 : el.opacity;
            options.fill = {
              color: c.color,
              transparency: (1 - c.alpha * opacity) * 100,
            };
          }
          if (el.defaultColor)
            options.color = formatColor(el.defaultColor).color;
          if (el.defaultFontName) options.fontFace = el.defaultFontName;
          if (el.shadow)
            options.shadow = getShadowOption(el.shadow, ratioPx2Pt);
          if (el.outline?.width)
            options.line = getOutlineOption(el.outline, ratioPx2Pt);
          if (el.opacity !== undefined)
            options.transparency = (1 - el.opacity) * 100;
          if (el.paragraphSpace !== undefined)
            options.paraSpaceBefore = el.paragraphSpace / ratioPx2Pt;
          if (el.vertical) options.vert = 'eaVert';

          pptxSlide.addText(textProps, options);
        } else if (el.type === 'image') {
          const options: pptxgen.ImageProps = {
            x: el.left / ratioPx2Inch,
            y: el.top / ratioPx2Inch,
            w: el.width / ratioPx2Inch,
            h: el.height / ratioPx2Inch,
          };
          if (isBase64Image(el.src)) options.data = el.src;
          else options.path = el.src;

          if (el.flipH) options.flipH = el.flipH;
          if (el.flipV) options.flipV = el.flipV;
          if (el.rotate) options.rotate = el.rotate;
          if (el.link) {
            const linkOption = getLinkOption(el.link, _slides);
            if (linkOption) options.hyperlink = linkOption;
          }
          // 处理透明度：优先使用元素透明度，如果没有则使用滤镜透明度
          if (el.opacity !== undefined) {
            options.transparency = (1 - el.opacity) * 100;
          } else if (el.filters?.opacity) {
            options.transparency = 100 - parseInt(el.filters?.opacity);
          }
          if (el.clip) {
            if (el.clip.shape === 'ellipse') options.rounding = true;

            const [start, end] = el.clip.range;
            const [startX, startY] = start;
            const [endX, endY] = end;

            // clip.range 中的值是百分比 (0-100)，需要转换为实际像素值
            const clipWidth = (endX - startX) / 100;
            const clipHeight = (endY - startY) / 100;

            const originW = el.width / clipWidth;
            const originH = el.height / clipHeight;

            options.w = originW / ratioPx2Inch;
            options.h = originH / ratioPx2Inch;

            options.sizing = {
              type: 'crop',
              x: ((startX / 100) * originW) / ratioPx2Inch,
              y: ((startY / 100) * originH) / ratioPx2Inch,
              w: (clipWidth * originW) / ratioPx2Inch,
              h: (clipHeight * originH) / ratioPx2Inch,
            };
          }

          pptxSlide.addImage(options);
        } else if (el.type === 'shape') {
          if (el.special) {
            // 特殊形状处理 - 使用替代方案
            const svgString = generateSvgString(el);
            if (svgString) {
              const base64SVG = svg2Base64(svgString);

              const options: pptxgen.ImageProps = {
                data: base64SVG,
                x: el.left / ratioPx2Inch,
                y: el.top / ratioPx2Inch,
                w: el.width / ratioPx2Inch,
                h: el.height / ratioPx2Inch,
              };
              if (el.rotate) options.rotate = el.rotate;
              if (el.flipH) options.flipH = el.flipH;
              if (el.flipV) options.flipV = el.flipV;
              if (el.link) {
                const linkOption = getLinkOption(el.link, _slides);
                if (linkOption) options.hyperlink = linkOption;
              }

              pptxSlide.addImage(options);
            }
          } else {
            const scale = {
              x: el.width / el.viewBox[0],
              y: el.height / el.viewBox[1],
            };
            const points = formatPoints(toPoints(el.path), ratioPx2Inch, scale);

            let fillColor = formatColor(el.fill);
            if (el.gradient) {
              const colors = el.gradient.colors;
              const color1 = colors[0].color;
              const color2 = colors[colors.length - 1].color;
              const color = tinycolor.mix(color1, color2).toHexString();
              fillColor = formatColor(color);
            }
            if (el.pattern) fillColor = formatColor('#00000000');
            const opacity = el.opacity === undefined ? 1 : el.opacity;

            const options: pptxgen.ShapeProps = {
              x: el.left / ratioPx2Inch,
              y: el.top / ratioPx2Inch,
              w: el.width / ratioPx2Inch,
              h: el.height / ratioPx2Inch,
              fill: {
                color: fillColor.color,
                transparency: (1 - fillColor.alpha * opacity) * 100,
              },
              points,
            };
            if (el.flipH) options.flipH = el.flipH;
            if (el.flipV) options.flipV = el.flipV;
            if (el.shadow)
              options.shadow = getShadowOption(el.shadow, ratioPx2Pt);
            if (el.outline?.width)
              options.line = getOutlineOption(el.outline, ratioPx2Pt);
            if (el.rotate) options.rotate = el.rotate;
            if (el.link) {
              const linkOption = getLinkOption(el.link, _slides);
              if (linkOption) options.hyperlink = linkOption;
            }

            pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options);
          }
          if (el.text) {
            const textProps = formatHTML(
              el.text.content,
              ratioPx2Pt,
              defaultFontSize
            );

            const options: pptxgen.TextPropsOptions = {
              x: el.left / ratioPx2Inch,
              y: el.top / ratioPx2Inch,
              w: el.width / ratioPx2Inch,
              h: el.height / ratioPx2Inch,
              fontSize: defaultFontSize / ratioPx2Pt,
              fontFace: '微软雅黑',
              color: '#000000',
              paraSpaceBefore: 5 / ratioPx2Pt,
              valign: el.text.align,
            };
            if (el.rotate) options.rotate = el.rotate;
            if (el.text.defaultColor)
              options.color = formatColor(el.text.defaultColor).color;
            if (el.text.defaultFontName)
              options.fontFace = el.text.defaultFontName;

            pptxSlide.addText(textProps, options);
          }
          if (el.pattern) {
            const options: pptxgen.ImageProps = {
              x: el.left / ratioPx2Inch,
              y: el.top / ratioPx2Inch,
              w: el.width / ratioPx2Inch,
              h: el.height / ratioPx2Inch,
            };
            if (isBase64Image(el.pattern)) options.data = el.pattern;
            else options.path = el.pattern;

            if (el.flipH) options.flipH = el.flipH;
            if (el.flipV) options.flipV = el.flipV;
            if (el.rotate) options.rotate = el.rotate;
            if (el.link) {
              const linkOption = getLinkOption(el.link, _slides);
              if (linkOption) options.hyperlink = linkOption;
            }

            pptxSlide.addImage(options);
          }
        } else if (el.type === 'line') {
          const path = getLineElementPath(el);
          const points = formatPoints(toPoints(path), ratioPx2Inch);
          const { minX, maxX, minY, maxY } = getElementRange(el);
          const c = formatColor(el.color);

          const options: pptxgen.ShapeProps = {
            x: el.left / ratioPx2Inch,
            y: el.top / ratioPx2Inch,
            w: (maxX - minX) / ratioPx2Inch,
            h: (maxY - minY) / ratioPx2Inch,
            line: {
              color: c.color,
              transparency: (1 - c.alpha) * 100,
              width: el.width / ratioPx2Pt,
              dashType: dashTypeMap[el.style] as 'solid' | 'dash' | 'sysDot',
              beginArrowType: el.points[0] ? 'arrow' : 'none',
              endArrowType: el.points[1] ? 'arrow' : 'none',
            },
            points,
          };
          if (el.shadow)
            options.shadow = getShadowOption(el.shadow, ratioPx2Pt);

          pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options);
        } else if (el.type === 'chart') {
          // 完整的图表处理逻辑
          const chartData = [];
          for (let i = 0; i < el.data.series.length; i++) {
            const item = el.data.series[i];
            chartData.push({
              name: `系列${i + 1}`,
              labels: el.data.labels,
              values: item,
            });
          }

          let chartColors: string[] = [];
          if (el.themeColors.length === 10)
            chartColors = el.themeColors.map(color => formatColor(color).color);
          else if (el.themeColors.length === 1) {
            chartColors = tinycolor(el.themeColors[0])
              .analogous(10)
              .map(color => formatColor(color.toHexString()).color);
          } else {
            const len = el.themeColors.length;
            const supplement = tinycolor(el.themeColors[len - 1])
              .analogous(10 + 1 - len)
              .map(color => color.toHexString());
            chartColors = [
              ...el.themeColors.slice(0, len - 1),
              ...supplement,
            ].map(color => formatColor(color).color);
          }

          const options: pptxgen.IChartOpts = {
            x: el.left / ratioPx2Inch,
            y: el.top / ratioPx2Inch,
            w: el.width / ratioPx2Inch,
            h: el.height / ratioPx2Inch,
            chartColors:
              el.chartType === 'pie' || el.chartType === 'ring'
                ? chartColors
                : chartColors.slice(0, el.data.series.length),
          };

          const textColor = formatColor(el.textColor || '#000000').color;
          options.catAxisLabelColor = textColor;
          options.valAxisLabelColor = textColor;

          const fontSize = 14 / ratioPx2Pt;
          options.catAxisLabelFontSize = fontSize;
          options.valAxisLabelFontSize = fontSize;

          if (el.fill || el.outline) {
            const plotArea: pptxgen.IChartPropsFillLine = {};
            if (el.fill) {
              plotArea.fill = { color: formatColor(el.fill).color };
            }
            if (el.outline) {
              plotArea.border = {
                pt: el.outline.width! / ratioPx2Pt,
                color: formatColor(el.outline.color!).color,
              };
            }
            options.plotArea = plotArea;
          }

          if (
            (el.data.series.length > 1 && el.chartType !== 'scatter') ||
            el.chartType === 'pie' ||
            el.chartType === 'ring'
          ) {
            options.showLegend = true;
            options.legendPos = 'b';
            options.legendColor = textColor;
            options.legendFontSize = fontSize;
          }

          let type = pptx.ChartType.bar;
          if (el.chartType === 'bar') {
            type = pptx.ChartType.bar;
            options.barDir = 'col';
            if (el.options?.stack) options.barGrouping = 'stacked';
          } else if (el.chartType === 'column') {
            type = pptx.ChartType.bar;
            options.barDir = 'bar';
            if (el.options?.stack) options.barGrouping = 'stacked';
          } else if (el.chartType === 'line') {
            type = pptx.ChartType.line;
            if (el.options?.lineSmooth) options.lineSmooth = true;
          } else if (el.chartType === 'area') {
            type = pptx.ChartType.area;
          } else if (el.chartType === 'radar') {
            type = pptx.ChartType.radar;
          } else if (el.chartType === 'scatter') {
            type = pptx.ChartType.scatter;
            options.lineSize = 0;
          } else if (el.chartType === 'pie') {
            type = pptx.ChartType.pie;
          } else if (el.chartType === 'ring') {
            type = pptx.ChartType.doughnut;
            options.holeSize = 60;
          }

          pptxSlide.addChart(type, chartData, options);
        } else if (el.type === 'table') {
          // 完整的表格处理逻辑
          const hiddenCells = [];
          for (let i = 0; i < el.data.length; i++) {
            const rowData = el.data[i];

            for (let j = 0; j < rowData.length; j++) {
              const cell = rowData[j];
              if (cell.colspan > 1 || cell.rowspan > 1) {
                for (let row = i; row < i + cell.rowspan; row++) {
                  for (
                    let col = row === i ? j + 1 : j;
                    col < j + cell.colspan;
                    col++
                  )
                    hiddenCells.push(`${row}_${col}`);
                }
              }
            }
          }

          const tableData = [];

          const theme = el.theme;
          let themeColor: FormatColor | null = null;
          let subThemeColors: FormatColor[] = [];
          if (theme) {
            themeColor = formatColor(theme.color);
            subThemeColors = getTableSubThemeColor(theme.color).map(item =>
              formatColor(item)
            );
          }

          for (let i = 0; i < el.data.length; i++) {
            const row = el.data[i];
            const _row = [];

            for (let j = 0; j < row.length; j++) {
              const cell = row[j];
              const cellOptions: pptxgen.TableCellProps = {
                colspan: cell.colspan,
                rowspan: cell.rowspan,
                bold: cell.style?.bold || false,
                italic: cell.style?.em || false,
                underline: { style: cell.style?.underline ? 'sng' : 'none' },
                align: cell.style?.align || 'left',
                valign: 'middle',
                fontFace: cell.style?.fontname || '微软雅黑',
                fontSize:
                  (cell.style?.fontsize ? parseInt(cell.style?.fontsize) : 14) /
                  ratioPx2Pt,
              };
              if (theme && themeColor) {
                let c: FormatColor;
                if (i % 2 === 0) c = subThemeColors[1];
                else c = subThemeColors[0];

                if (theme.rowHeader && i === 0) c = themeColor;
                else if (theme.rowFooter && i === el.data.length - 1)
                  c = themeColor;
                else if (theme.colHeader && j === 0) c = themeColor;
                else if (theme.colFooter && j === row.length - 1)
                  c = themeColor;

                cellOptions.fill = {
                  color: c.color,
                  transparency: (1 - c.alpha) * 100,
                };
              }
              if (cell.style?.backcolor) {
                const c = formatColor(cell.style.backcolor);
                cellOptions.fill = {
                  color: c.color,
                  transparency: (1 - c.alpha) * 100,
                };
              }
              if (cell.style?.color)
                cellOptions.color = formatColor(cell.style.color).color;

              if (!hiddenCells.includes(`${i}_${j}`)) {
                _row.push({
                  text: cell.text,
                  options: cellOptions,
                });
              }
            }
            if (_row.length) tableData.push(_row);
          }

          const options: pptxgen.TableProps = {
            x: el.left / ratioPx2Inch,
            y: el.top / ratioPx2Inch,
            w: el.width / ratioPx2Inch,
            h: el.height / ratioPx2Inch,
            colW: el.colWidths.map(item => (el.width * item) / ratioPx2Inch),
          };
          if (el.theme) options.fill = { color: '#ffffff' };
          if (el.outline.width && el.outline.color) {
            options.border = {
              type: el.outline.style === 'solid' ? 'solid' : 'dash',
              pt: el.outline.width / ratioPx2Pt,
              color: formatColor(el.outline.color).color,
            };
          }

          pptxSlide.addTable(tableData, options);
        } else if (el.type === 'latex') {
          // LaTeX 处理 - 使用替代方案
          const svgString = generateSvgString(el);
          if (svgString) {
            const base64SVG = svg2Base64(svgString);

            const options: pptxgen.ImageProps = {
              data: base64SVG,
              x: el.left / ratioPx2Inch,
              y: el.top / ratioPx2Inch,
              w: el.width / ratioPx2Inch,
              h: el.height / ratioPx2Inch,
            };
            if (el.link) {
              const linkOption = getLinkOption(el.link, _slides);
              if (linkOption) options.hyperlink = linkOption;
            }

            pptxSlide.addImage(options);
          }
        } else if (
          !ignoreMedia &&
          (el.type === 'video' || el.type === 'audio')
        ) {
          // 完整的媒体处理逻辑
          const options: pptxgen.MediaProps = {
            x: el.left / ratioPx2Inch,
            y: el.top / ratioPx2Inch,
            w: el.width / ratioPx2Inch,
            h: el.height / ratioPx2Inch,
            path: el.src,
            type: el.type,
          };
          if (el.type === 'video' && el.poster) options.cover = el.poster;

          const extMatch = el.src.match(/\.([a-zA-Z0-9]+)(?:[\?#]|$)/);
          if (extMatch && extMatch[1]) options.extn = extMatch[1];
          else if (el.ext) options.extn = el.ext;

          const videoExts = ['avi', 'mp4', 'm4v', 'mov', 'wmv'];
          const audioExts = ['mp3', 'm4a', 'mp4', 'wav', 'wma'];
          if (
            options.extn &&
            [...videoExts, ...audioExts].includes(options.extn)
          ) {
            pptxSlide.addMedia(options);
          }
        }
      }
    }

    // 设置PPTX元数据
    pptx.author = 'PPTX Exporter';
    pptx.company = 'Devoink';
    pptx.title = this.pptData.title || 'Untitled Presentation';

    this.reportProgress(totalSlides, totalSlides, true);

    return pptx;
  }

  /**
   * 导出PPTX文件
   * @param fileName 文件名
   * @param compression 是否启用压缩
   * @returns 导出的文件
   * @throws 在浏览器环境中会触发下载，在Node.js环境中会保存到磁盘
   */
  public toFile(
    fileName: string = `${this.pptData.title}.pptx`,
    compression: boolean = false
  ) {
    if (EnvironmentDetector.isBrowser()) {
      // 浏览器环境：触发下载
      console.log(`File download will be triggered: ${fileName}`);
    } else if (EnvironmentDetector.isNodeJS()) {
      // Node.js环境：保存到磁盘
      console.log(`File will be saved to disk: ${fileName}`);
    } else {
      console.log(
        `File export in ${this.environment} environment: ${fileName}`
      );
    }

    return this._export().writeFile({
      fileName,
      compression,
    });
  }

  /**
   * 导出PPTX文件为Buffer (仅Node.js/Bun环境)
   * @returns 导出的Buffer
   * @throws 在不支持Buffer的环境中会抛出错误
   */
  public toBuffer(): Promise<Buffer> {
    if (!EnvironmentDetector.supportsBuffer()) {
      return Promise.reject(
        new Error(
          `Buffer not supported in ${this.environment} environment. Use toArrayBuffer() or toUint8Array() instead.`
        )
      );
    }

    try {
      return this._export().write({
        outputType: 'nodebuffer',
      }) as unknown as Promise<Buffer>;
    } catch (error) {
      return Promise.reject(error);
    }
  }

  /**
   * 导出PPTX文件为Blob (仅浏览器/Deno环境)
   * @param compression 是否启用压缩
   * @returns 导出的Blob
   * @throws 在不支持Blob的环境中会抛出错误
   */
  public toBlob(compression: boolean = false): Promise<Blob> {
    if (!EnvironmentDetector.supportsBlob()) {
      return Promise.reject(
        new Error(
          `Blob not supported in ${this.environment} environment. Use toBuffer() or toArrayBuffer() instead.`
        )
      );
    }

    return this._export().write({
      outputType: 'blob',
      compression,
    }) as unknown as Promise<Blob>;
  }

  /**
   * 导出PPTX文件为Base64字符串
   * @param compression 是否启用压缩
   * @returns 导出的Base64字符串
   */
  public toBase64(compression: boolean = false): Promise<string> {
    return this._export().write({
      outputType: 'base64',
      compression,
    }) as unknown as Promise<string>;
  }

  /**
   * 导出PPTX文件为ArrayBuffer
   * @param compression 是否启用压缩
   * @returns 导出的ArrayBuffer
   */
  public toArrayBuffer(compression: boolean = false): Promise<ArrayBuffer> {
    return this._export().write({
      outputType: 'arraybuffer',
      compression,
    }) as unknown as Promise<ArrayBuffer>;
  }

  /**
   * 导出PPTX文件为BinaryString
   * @param compression 是否启用压缩
   * @returns 导出的BinaryString
   */
  public toBinaryString(compression: boolean = false): Promise<string> {
    return this._export().write({
      outputType: 'binarystring',
      compression,
    }) as unknown as Promise<string>;
  }

  /**
   * 导出PPTX文件为Uint8Array
   * @param compression 是否启用压缩
   * @returns 导出的Uint8Array
   */
  public toUint8Array(compression: boolean = false): Promise<Uint8Array> {
    return this._export().write({
      outputType: 'uint8array',
      compression,
    }) as unknown as Promise<Uint8Array>;
  }

  /**
   * 导出PPTX文件为流（仅Node.js环境）
   * @returns 导出的二进制字符串，适用于HTTP响应流
   * @throws 在非Node.js环境中会抛出错误
   */
  public toStream(): Promise<string> {
    if (!EnvironmentDetector.isNodeJS()) {
      return Promise.reject(
        new Error(
          `Stream export not supported in ${this.environment} environment. Only available in Node.js.`
        )
      );
    }

    return this._export().stream() as unknown as Promise<string>;
  }

  /**
   * 通用导出方法，支持所有输出格式
   * @param options 导出选项
   * @returns 导出的数据
   * @throws 在不支持指定格式的环境中会抛出错误
   */
  public export(options: {
    outputType:
      | 'blob'
      | 'arraybuffer'
      | 'base64'
      | 'binarystring'
      | 'nodebuffer'
      | 'uint8array';
    compression?: boolean;
  }): Promise<any> {
    // 检查环境兼容性
    if (
      options.outputType === 'nodebuffer' &&
      !EnvironmentDetector.supportsBuffer()
    ) {
      return Promise.reject(
        new Error(
          `nodebuffer not supported in ${this.environment} environment. Use arraybuffer or uint8array instead.`
        )
      );
    }

    if (options.outputType === 'blob' && !EnvironmentDetector.supportsBlob()) {
      return Promise.reject(
        new Error(
          `blob not supported in ${this.environment} environment. Use buffer or arraybuffer instead.`
        )
      );
    }

    if (
      options.outputType === 'arraybuffer' &&
      !EnvironmentDetector.supportsArrayBuffer()
    ) {
      return Promise.reject(
        new Error(
          `arraybuffer not supported in ${this.environment} environment.`
        )
      );
    }

    if (
      options.outputType === 'uint8array' &&
      !EnvironmentDetector.supportsUint8Array()
    ) {
      return Promise.reject(
        new Error(
          `uint8array not supported in ${this.environment} environment.`
        )
      );
    }

    return this._export().write(options);
  }

  /**
   * 智能导出方法，根据环境自动选择最佳格式
   * @param compression 是否启用压缩
   * @returns 导出的数据
   */
  public smartExport(compression: boolean = false): Promise<any> {
    if (EnvironmentDetector.supportsBuffer()) {
      return this.toBuffer();
    } else if (EnvironmentDetector.supportsBlob()) {
      return this.toBlob(compression);
    } else if (EnvironmentDetector.supportsArrayBuffer()) {
      return this.toArrayBuffer(compression);
    } else if (EnvironmentDetector.supportsUint8Array()) {
      return this.toUint8Array(compression);
    } else {
      // 最后回退到 base64
      return this.toBase64(compression);
    }
  }
}
